package com.sneakxy.mybatis.commons.repository.support;

import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.ResultType;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.query.ResultProcessor;

import com.sneakxy.mybatis.commons.orm.MybatisEntityManager;
import com.sneakxy.mybatis.commons.repository.query.MybatisQueryMethod;
import com.sneakxy.mybatis.commons.repository.query.parser.MybatisPartTree;
import com.sneakxy.mybatis.commons.utils.MybatisUtils;

/**
 * @author 潜行的青衣
 */
public class MybatisNamedRepositoryQuery extends AbstractMybatisRepositoryQuery {

	private NamedQueries namedQueries;
	
	private String statement;
	
	private String pageCountQueryId;
	
	private MybatisPartTree partTree;
	
	/**
	 * @param entityManager
	 * @param queryMethod
	 * @param namedQueries
	 */
	public MybatisNamedRepositoryQuery(
			MybatisEntityManager entityManager, MybatisQueryMethod queryMethod,
			NamedQueries namedQueries) {
		super(entityManager, queryMethod);
		this.namedQueries = namedQueries;
		this.partTree = new MybatisPartTree(entityManager, this.getMethod().getName());
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public Object execute(Object[] parameters) {
		ResultProcessor processor = this.getQueryMethod().getResultProcessor();
		Object parameterObject = this.createParameterObject(parameters, false);
		MappedStatement mappedStatement = this.getEntityManager().getConfiguration().getMappedStatement(this.statement);
		SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
		SqlSessionTemplate template = this.getEntityManager().getSqlSessionTemplate();
		if(sqlCommandType == SqlCommandType.SELECT) {
			if(this.getQueryMethod().isCollectionQuery()) {
				return template.selectList(statement, parameterObject);
			} else if(this.getQueryMethod().isPageQuery() || this.getQueryMethod().isSliceQuery()) {
				List<Object> content = template.selectList(statement, parameterObject);
				Long total = template.selectOne(this.getPageCountQueryId(), createParameterObject(parameters, true));
				return new PageImpl(content, MybatisUtils.getPageable(parameters), total);
			} else {
				List<Object> results = template.selectList(statement, parameterObject);
				return processor.processResult(results.size() > 0 ? results.get(0) : null);
			}
		} else if(sqlCommandType == SqlCommandType.INSERT) {
			return processor.processResult(template.insert(statement, parameterObject));
		} else if(sqlCommandType == SqlCommandType.UPDATE) {
			return processor.processResult(template.update(statement, parameterObject));
		} else if(sqlCommandType == SqlCommandType.DELETE) {
			return processor.processResult(template.delete(statement, parameterObject));
		}
		if(this.getQueryMethod().getReturnedObjectType().isPrimitive()) {
			return processor.processResult(0);
		}
		return processor.processResult(null);
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		this.doCreateStatement();
		super.afterPropertiesSet();
	}
	
	protected void doCreateStatement() {
		String methodName = this.getMethod().getName();
		Class<?> domainClass = this.getQueryMethod().getReturnedObjectType();
		StringBuilder sqlBuilder = new StringBuilder();
		String tableName = this.getEntityManager().getTablename();
		SqlCommandType sqlCommandType  = SqlCommandType.SELECT;
		String id = this.createStatementId(methodName);
		this.statement = this.getEntityManager().getNamespacedId(id);
		if(partTree.isDelete()) {
			sqlBuilder.append("DELETE FROM ");
			sqlCommandType = SqlCommandType.DELETE;
		} else if(partTree.isCountProjection()) {
			sqlBuilder.append("SELECT COUNT(*) FROM ");
		} else if(partTree.isDistinct()) {
			sqlBuilder.append("SELECT DISTINCT(*) FROM ");
		} else {
			sqlBuilder.append("SELECT * FROM ");
		}
		sqlBuilder.append(tableName);
		SqlSource sqlSource = this.partTree.buildSqlSource(sqlBuilder.toString());
		if(partTree.isDelete()) {
			this.getEntityManager().addMappedStatement(id, sqlSource, sqlCommandType, ParamMap.class, Integer.class);
		} else if(partTree.isCountProjection()) {
			this.getEntityManager().addMappedStatement(id, sqlSource, sqlCommandType, ParamMap.class, Long.class);
		} else {
			String resultMapName = domainClass.getSimpleName();
			Class<?> resultTypeClass = domainClass;
			ResultMap resultMap = this.getMethod().getAnnotation(ResultMap.class);
			if(resultMap != null && resultMap.value() != null) {
				StringBuilder resultMapBuilder = new StringBuilder();
				for(String value:resultMap.value()) {
					resultMapBuilder.append(value);
				}
				String t_resultMapName = resultMapBuilder.toString();
				if(StringUtils.isNoneBlank(t_resultMapName)) {
					resultMapName = t_resultMapName;
				}
			}
			ResultType resultType = this.getMethod().getAnnotation(ResultType.class);
			if(resultType != null && resultType.value() != null) {
				resultTypeClass = resultType.value();
			}
			this.getEntityManager().addMappedStatement(id, sqlSource, sqlCommandType, ParamMap.class, resultMapName, resultTypeClass);
		}
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.support.AbstractMybatisRepositoryQuery#doCreatePageCountQuery()
	 */
	@Override
	protected void doCreatePageCountQuery() {
		if(!this.partTree.isDelete() && !this.partTree.isCountProjection()) {
			String tableName = this.getEntityManager().getTablename();
			StringBuilder sqlBuilder = new StringBuilder();
			if(partTree.isDistinct()) {
				sqlBuilder.append("SELECT COUNT(DISTINCT(*)) FROM ");
			} else {
				sqlBuilder.append("SELECT COUNT(*) FROM ");
			}
			sqlBuilder.append(tableName);
			SqlSource sqlSource = this.partTree.buildSqlSource(sqlBuilder.toString());
			String id = this.createMapperPageCountId();
			SqlCommandType sqlCommandType  = SqlCommandType.SELECT;
			this.getEntityManager().addMappedStatement(
					id, sqlSource, sqlCommandType, 
					ParamMap.class, Long.class);
		}
	}
	
	protected String createMapperPageCountId() {
		StringBuilder idBuilder = new StringBuilder()
				.append("count_").append(this.getQueryMethod().getName()).append("-");
		String resultId = this.createStatementId(idBuilder.toString());
		this.pageCountQueryId = this.getEntityManager().getNamespacedId(resultId);
		return resultId;
	}

	public NamedQueries getNamedQueries() {
		return namedQueries;
	}

	public String getStatement() {
		return statement;
	}

	public String getPageCountQueryId() {
		return pageCountQueryId;
	}

}
